#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <assert.h>

#include "../glib/ghash.h"
#include "network.h"
#include "edge.h"

struct sNetwork{
    GHashTable *nodes; /* Tabla con los nodos del network */
};

/* FUNCIONES AUXILIARES */

static void memory_problem(network net);
static void reset_level(gpointer key, gpointer value, gpointer user_data);
static void print_neighFlow(gpointer key, gpointer value, gpointer user_data);
static void print_edge(gpointer data, gpointer user_data);

network network_create(void){
    network net = NULL;

    /* Asigno memoria para alojar la estructura */
    net = (network) malloc(sizeof(struct sNetwork));
    if (net == NULL){
        memory_problem(net);
    }
    /* Creo la tabla de nodos */
    net->nodes = g_hash_table_new_full(g_intP_hash, g_intP_equal, NULL,
                                            (GDestroyNotify) node_destroy);

    assert(net != NULL);
    assert(net->nodes != NULL);

    return net;
}

network network_destroy(network net){
    assert(net != NULL);
    assert(net->nodes != NULL);

    /* Destruyo la tabla de nodos */
    g_hash_table_destroy(net->nodes);
    net->nodes = NULL;

    /* Libero la memoria alojada por el network */
    free(net);
    net = NULL;

    assert(net == NULL);
    return net;
}

void network_addEdge(network net, const unsigned int label1,
                         const unsigned int label2, const unsigned int cap){
    node n1 = NULL, n2 = NULL;
    assert(net != NULL);
    assert(net->nodes != NULL);

    /* Si los nodos no estaban en el network, los agrego a la tabla */
    if (!network_nodeExists(net, label1))
        network_addNode(net, label1);
    if (!network_nodeExists(net, label2))
        network_addNode(net, label2);

    /* Obtengo los nodos asociados a los labels */
    n1 = network_getNode(net, label1);
    n2 = network_getNode(net, label2);

    /* Agrego 'n2' a los vecinos de 'n1' */
    if (!node_addNeighbor(n1, n2, cap)){
        memory_problem(net);
    }
}

void network_addNode(network net, const unsigned int label){
    node n = NULL;

    assert(net != NULL);
    assert(net->nodes != NULL);
    assert(!network_nodeExists(net, label));

    /* Creo el nodo */
    n = node_create(label);

    if(n == NULL){
        memory_problem(net);
    }

    /* Agrego el nodo a la tabla de nodos del network */
    g_hash_table_insert(net->nodes, node_getLabelAddress(n), n);
}

node network_getNode(network net, const unsigned int label){
    assert(net != NULL);
    assert(net->nodes != NULL);
    assert(network_nodeExists(net, label));

    return g_hash_table_lookup(net->nodes, &label);
}

GList *network_getNeighbors(const network net, const unsigned int label){
    node n = NULL;

    assert(net != NULL);
    assert(net->nodes != NULL);

    /* Obtengo el nodo asociado a 'label' */
    n = network_getNode(net, label);

    assert(n != NULL);

    /* Devuelvo sus vecinos */
    return node_getNeighbors(n);
}

GList *network_getBackwardNeighbors(const network net, const unsigned int label){
    node n = NULL;

    assert(net != NULL);
    assert(net->nodes != NULL);

    /* Obtengo el nodo asociado a 'label' */
    n = network_getNode(net, label);

    assert(n != NULL);

    /* Devuelvo sus vecinos backward */
    return node_getBackwardNeighbors(n);
}

bool network_nodeExists(const network net, const unsigned int label){
    assert(net != NULL);
    assert(net->nodes != NULL);

    return (g_hash_table_lookup(net->nodes, &label) != NULL);
}

bool network_edgeExists(const network net, const unsigned int label_a,
                                                 const unsigned int label_b){
    node n = NULL;

    assert(net != NULL);
    assert(net->nodes != NULL);
    assert(network_nodeExists(net, label_a));
    assert(network_nodeExists(net, label_b));

    /* Obtengo el nodo asociado a 'label_a' */
    n = network_getNode(net, label_a);

    assert(n != NULL);

    return node_neighborExists(n, label_b);
}

unsigned int network_edgeCap(const network net, const unsigned int label_a,
                                                 const unsigned int label_b){
    node n = NULL;

    assert(net != NULL);
    assert(net->nodes != NULL);
    assert(network_edgeExists(net, label_a, label_b));

    /* Obtengo el nodo asociado a 'label_a' */
    n = network_getNode(net, label_a);

    assert(n!= NULL);

    return node_getEdgeCap(n, label_b);
}

void network_printFlow(network net){
    g_hash_table_foreach(net->nodes, print_neighFlow, NULL);
}

static void print_neighFlow(gpointer key, gpointer value, gpointer user_data){
    node n = (node) value;
    (void) key;
    (void) user_data;
    g_list_foreach(node_getNeighbors(n), print_edge, n);
}

static void print_edge(gpointer data, gpointer user_data){
    edge e = (edge) data;
    printf("%u\t%u\t%u (%u)\n",node_getLabel((node) user_data),
         node_getLabel(edge_getNeighbor(e)),edge_getFlow(e),edge_getCap(e));
}

void network_initializeLevels(network net){
    g_hash_table_foreach(net->nodes, (GHFunc) reset_level, NULL);
}

static void reset_level(gpointer key, gpointer value, gpointer user_data){
    (void) key;
    (void) user_data;
    node_setLevel((node) value, -1);
}

/* Si falla malloc, libera el network y avisa el error por pantalla */
static void memory_problem(network net){
    assert(net != NULL);

    net = network_destroy(net);
    assert(net == NULL);

    errx(EXIT_FAILURE, "Memoria insuficiente");
}
